#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
基于腾讯云HAI免费额度的智能扩缩容策略
充分利用80GB免费硬盘和500GB免费流量，实现成本优化
"""

import time
import json
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
from enum import Enum
import statistics

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class UserTier(Enum):
    """用户等级枚举"""
    FREE = "free"
    PAID = "paid"
    BATCH = "batch"

class ProcessingMode(Enum):
    """处理模式枚举"""
    REALTIME = "realtime"  # 实时处理
    BATCH = "batch"        # 批处理
    NIGHT_BATCH = "night_batch"  # 夜间批处理

@dataclass
class FreeQuotaStatus:
    """免费额度状态"""
    disk_usage_gb: float
    network_usage_gb: float
    days_since_reset: int
    last_reset_date: datetime
    restart_needed: bool
    restart_reason: str

@dataclass
class InstanceInfo:
    """实例信息"""
    instance_id: str
    start_time: datetime
    disk_usage_gb: float
    network_usage_gb: float
    cpu_utilization: float
    memory_utilization: float
    status: str
    cost_accumulated: float

@dataclass
class ScalingMetrics:
    """扩缩容指标"""
    queue_length: int
    free_queue_length: int
    paid_queue_length: int
    average_wait_time: float
    current_instances: int
    free_quota_status: FreeQuotaStatus
    time_of_day: int  # 0-23小时
    is_weekend: bool
    is_holiday: bool

class FreeQuotaScalingStrategy:
    """基于免费额度的智能扩缩容策略"""
    
    def __init__(self):
        self.config = self._load_config()
        self.free_quota_monitor = FreeQuotaMonitor()
        self.night_processor = NightBatchProcessor()
        
    def _load_config(self) -> Dict:
        """加载配置"""
        return {
            'free_quota': {
                'disk_limit_gb': 80,
                'network_limit_gb': 500,
                'restart_interval_days': 15,
                'maintenance_window': (2, 4),  # 02:00-04:00
                'warning_thresholds': {
                    'disk': 70,  # 70GB时警告
                    'network': 400,  # 400GB时警告
                },
                'critical_thresholds': {
                    'disk': 75,  # 75GB时准备重启
                    'network': 450,  # 450GB时限制
                }
            },
            'night_processing': {
                'processing_window': (23, 6),  # 23:00-06:00
                'batch_size': 50,
                'min_instances': 1,
                'free_task_priority': 2,
                'notification_method': 'wechat_template',
            },
            'scaling': {
                'paid_user': {
                    'max_instances': 10,
                    'scale_out_threshold': {
                        'queue_length': 3,
                        'wait_time': 60,
                    },
                    'scale_in_threshold': {
                        'idle_time': 300,
                        'queue_empty': False,
                    },
                    'cooldown_period': 30,
                },
                'free_user': {
                    'max_instances': 1,
                    'scale_out_threshold': {
                        'queue_length': 20,
                        'wait_time': 300,
                    },
                    'scale_in_threshold': {
                        'idle_time': 60,
                        'queue_empty': True,
                    },
                    'cooldown_period': 60,
                }
            },
            'pricing': {
                'base_price': 0.3,
                'load_multipliers': {
                    'low': 0.8,
                    'normal': 1.0,
                    'high': 1.2,
                    'peak': 1.5,
                },
                'time_discounts': {
                    'night': 0.7,
                    'weekend': 0.9,
                }
            }
        }
    
    def analyze_free_quota_status(self, instance_info: InstanceInfo) -> FreeQuotaStatus:
        """分析免费额度状态"""
        days_since_reset = (datetime.now() - instance_info.start_time).days
        
        restart_needed = False
        restart_reason = ""
        
        # 检查重启条件
        if days_since_reset >= self.config['free_quota']['restart_interval_days']:
            restart_needed = True
            restart_reason = "15天周期重启"
        elif instance_info.disk_usage_gb >= self.config['free_quota']['critical_thresholds']['disk']:
            restart_needed = True
            restart_reason = "硬盘使用接近限制"
        elif instance_info.network_usage_gb >= self.config['free_quota']['critical_thresholds']['network']:
            restart_needed = True
            restart_reason = "网络使用接近限制"
        
        return FreeQuotaStatus(
            disk_usage_gb=instance_info.disk_usage_gb,
            network_usage_gb=instance_info.network_usage_gb,
            days_since_reset=days_since_reset,
            last_reset_date=instance_info.start_time,
            restart_needed=restart_needed,
            restart_reason=restart_reason
        )
    
    def should_restart_instance(self, instance_info: InstanceInfo) -> Tuple[bool, str]:
        """判断是否应该重启实例"""
        free_quota_status = self.analyze_free_quota_status(instance_info)
        
        if free_quota_status.restart_needed:
            # 检查是否在维护时间窗口内
            current_hour = datetime.now().hour
            maintenance_start, maintenance_end = self.config['free_quota']['maintenance_window']
            
            if maintenance_start <= current_hour <= maintenance_end:
                return True, free_quota_status.restart_reason
            else:
                return False, f"需要重启但不在维护窗口内 (当前时间: {current_hour}:00)"
        
        return False, "无需重启"
    
    def get_night_processing_strategy(self, metrics: ScalingMetrics) -> Dict:
        """获取夜间处理策略"""
        current_hour = metrics.time_of_day
        processing_start, processing_end = self.config['night_processing']['processing_window']
        
        is_night_time = current_hour >= processing_start or current_hour < processing_end
        
        strategy = {
            'is_night_time': is_night_time,
            'should_process_free_tasks': False,
            'min_instances': 1,
            'batch_size': self.config['night_processing']['batch_size'],
            'processing_mode': ProcessingMode.REALTIME,
        }
        
        if is_night_time:
            strategy.update({
                'should_process_free_tasks': True,
                'processing_mode': ProcessingMode.NIGHT_BATCH,
                'min_instances': self.config['night_processing']['min_instances'],
            })
        
        return strategy
    
    def calculate_optimal_instances(self, metrics: ScalingMetrics) -> int:
        """计算最优实例数"""
        night_strategy = self.get_night_processing_strategy(metrics)
        
        # 基础实例数计算
        if night_strategy['is_night_time']:
            # 夜间模式：最少实例数 + 免费任务处理
            base_instances = night_strategy['min_instances']
            
            # 如果有付费用户任务，增加实例
            if metrics.paid_queue_length > 0:
                paid_instances = min(3, (metrics.paid_queue_length + 4) // 5)
                base_instances = max(base_instances, paid_instances)
        else:
            # 白天模式：基于负载计算
            base_instances = max(1, (metrics.queue_length + 4) // 5)
            
            # 付费用户优先
            if metrics.paid_queue_length > 0:
                paid_instances = min(10, (metrics.paid_queue_length + 2) // 3)
                base_instances = max(base_instances, paid_instances)
        
        # 应用免费额度限制
        if metrics.free_quota_status.restart_needed:
            # 如果需要重启，减少实例数
            base_instances = max(1, base_instances // 2)
        
        return base_instances
    
    def get_scaling_plan(self, metrics: ScalingMetrics) -> Dict:
        """获取扩缩容计划"""
        optimal_instances = self.calculate_optimal_instances(metrics)
        night_strategy = self.get_night_processing_strategy(metrics)
        
        plan = {
            'current_instances': metrics.current_instances,
            'target_instances': optimal_instances,
            'scaling_action': 'maintain',
            'scaling_amount': 0,
            'reason': 'No scaling needed',
            'confidence': 0.5,
            'free_quota_status': {
                'restart_needed': metrics.free_quota_status.restart_needed,
                'restart_reason': metrics.free_quota_status.restart_reason,
                'disk_usage_gb': metrics.free_quota_status.disk_usage_gb,
                'network_usage_gb': metrics.free_quota_status.network_usage_gb,
            },
            'night_processing': night_strategy,
            'cost_optimization': {
                'free_quota_utilization': self._calculate_free_quota_utilization(metrics.free_quota_status),
                'estimated_savings': self._calculate_estimated_savings(metrics),
            }
        }
        
        if optimal_instances > metrics.current_instances:
            plan.update({
                'scaling_action': 'scale_out',
                'scaling_amount': optimal_instances - metrics.current_instances,
                'reason': f'Load increase detected (queue: {metrics.queue_length}, paid: {metrics.paid_queue_length})',
            })
        elif optimal_instances < metrics.current_instances:
            plan.update({
                'scaling_action': 'scale_in',
                'scaling_amount': metrics.current_instances - optimal_instances,
                'reason': f'Load decrease detected (queue: {metrics.queue_length})',
            })
        
        return plan
    
    def _calculate_free_quota_utilization(self, free_quota_status: FreeQuotaStatus) -> Dict:
        """计算免费额度利用率"""
        disk_utilization = (free_quota_status.disk_usage_gb / self.config['free_quota']['disk_limit_gb']) * 100
        network_utilization = (free_quota_status.network_usage_gb / self.config['free_quota']['network_limit_gb']) * 100
        
        return {
            'disk_utilization_percent': disk_utilization,
            'network_utilization_percent': network_utilization,
            'days_until_restart': self.config['free_quota']['restart_interval_days'] - free_quota_status.days_since_reset,
            'efficiency_score': (disk_utilization + network_utilization) / 2,
        }
    
    def _calculate_estimated_savings(self, metrics: ScalingMetrics) -> Dict:
        """计算预估节省"""
        # 基于免费额度使用情况计算节省
        disk_savings = metrics.free_quota_status.disk_usage_gb * 0.1  # 假设每GB节省0.1元
        network_savings = metrics.free_quota_status.network_usage_gb * 0.8  # 每GB节省0.8元
        
        total_savings = disk_savings + network_savings
        
        return {
            'disk_savings': disk_savings,
            'network_savings': network_savings,
            'total_savings': total_savings,
            'savings_per_day': total_savings / max(1, metrics.free_quota_status.days_since_reset),
        }
    
    def get_dynamic_pricing(self, metrics: ScalingMetrics) -> Dict:
        """获取动态定价"""
        base_price = self.config['pricing']['base_price']
        
        # 基于负载的倍数
        if metrics.queue_length <= 5:
            load_multiplier = self.config['pricing']['load_multipliers']['low']
        elif metrics.queue_length <= 15:
            load_multiplier = self.config['pricing']['load_multipliers']['normal']
        elif metrics.queue_length <= 30:
            load_multiplier = self.config['pricing']['load_multipliers']['high']
        else:
            load_multiplier = self.config['pricing']['load_multipliers']['peak']
        
        # 基于时间的折扣
        time_discount = 1.0
        if self.get_night_processing_strategy(metrics)['is_night_time']:
            time_discount = self.config['pricing']['time_discounts']['night']
        elif metrics.is_weekend:
            time_discount = self.config['pricing']['time_discounts']['weekend']
        
        final_price = base_price * load_multiplier * time_discount
        
        return {
            'base_price': base_price,
            'load_multiplier': load_multiplier,
            'time_discount': time_discount,
            'final_price': final_price,
            'pricing_reason': f'负载倍数: {load_multiplier}, 时间折扣: {time_discount}',
        }

class FreeQuotaMonitor:
    """免费额度监控器"""
    
    def __init__(self):
        self.quota_history = []
    
    def monitor_quota_usage(self, instance_info: InstanceInfo) -> Dict:
        """监控额度使用情况"""
        current_usage = {
            'timestamp': datetime.now(),
            'disk_usage_gb': instance_info.disk_usage_gb,
            'network_usage_gb': instance_info.network_usage_gb,
            'cost_accumulated': instance_info.cost_accumulated,
        }
        
        self.quota_history.append(current_usage)
        
        # 保持历史记录在合理范围内
        if len(self.quota_history) > 1000:
            self.quota_history = self.quota_history[-500:]
        
        return current_usage
    
    def get_quota_trends(self) -> Dict:
        """获取额度使用趋势"""
        if len(self.quota_history) < 2:
            return {'trend': 'insufficient_data'}
        
        recent_usage = self.quota_history[-10:]  # 最近10次记录
        
        disk_trend = self._calculate_trend([u['disk_usage_gb'] for u in recent_usage])
        network_trend = self._calculate_trend([u['network_usage_gb'] for u in recent_usage])
        cost_trend = self._calculate_trend([u['cost_accumulated'] for u in recent_usage])
        
        return {
            'disk_trend': disk_trend,
            'network_trend': network_trend,
            'cost_trend': cost_trend,
            'predicted_disk_usage': self._predict_usage([u['disk_usage_gb'] for u in recent_usage]),
            'predicted_network_usage': self._predict_usage([u['network_usage_gb'] for u in recent_usage]),
        }
    
    def _calculate_trend(self, values: List[float]) -> str:
        """计算趋势"""
        if len(values) < 2:
            return 'stable'
        
        first_half = statistics.mean(values[:len(values)//2])
        second_half = statistics.mean(values[len(values)//2:])
        
        change_percent = ((second_half - first_half) / first_half) * 100
        
        if change_percent > 10:
            return 'increasing'
        elif change_percent < -10:
            return 'decreasing'
        else:
            return 'stable'
    
    def _predict_usage(self, values: List[float]) -> float:
        """预测使用量"""
        if len(values) < 3:
            return values[-1] if values else 0
        
        # 简单线性预测
        recent_values = values[-5:]
        trend = (recent_values[-1] - recent_values[0]) / len(recent_values)
        predicted = recent_values[-1] + trend * 3  # 预测3个周期后
        
        return max(0, predicted)

class NightBatchProcessor:
    """夜间批处理器"""
    
    def __init__(self):
        self.free_task_queue = []
        self.processing_history = []
    
    def add_free_task(self, task: Dict):
        """添加免费任务到队列"""
        task['added_time'] = datetime.now()
        task['priority'] = 2  # 免费任务优先级较低
        self.free_task_queue.append(task)
        
        logger.info(f"Added free task to night batch queue: {task['task_id']}")
    
    def process_night_batch(self, available_instances: int) -> List[Dict]:
        """处理夜间批处理任务"""
        if not self.free_task_queue:
            return []
        
        # 按优先级和添加时间排序
        self.free_task_queue.sort(key=lambda x: (x['priority'], x['added_time']))
        
        # 计算可处理的任务数量
        max_tasks = min(len(self.free_task_queue), available_instances * 10)
        tasks_to_process = self.free_task_queue[:max_tasks]
        self.free_task_queue = self.free_task_queue[max_tasks:]
        
        results = []
        for task in tasks_to_process:
            result = self._process_single_task(task)
            results.append(result)
            
            # 记录处理历史
            self.processing_history.append({
                'task_id': task['task_id'],
                'processed_time': datetime.now(),
                'processing_time': result['processing_time'],
                'success': result['success'],
            })
        
        logger.info(f"Processed {len(results)} free tasks in night batch")
        return results
    
    def _process_single_task(self, task: Dict) -> Dict:
        """处理单个任务"""
        # 模拟任务处理
        processing_time = 30 + (hash(task['task_id']) % 20)  # 30-50秒
        
        return {
            'task_id': task['task_id'],
            'user_id': task['user_id'],
            'processing_time': processing_time,
            'success': True,
            'result_url': f"https://example.com/results/{task['task_id']}",
            'processed_time': datetime.now(),
        }
    
    def get_queue_status(self) -> Dict:
        """获取队列状态"""
        return {
            'queue_length': len(self.free_task_queue),
            'oldest_task_age': self._get_oldest_task_age(),
            'estimated_processing_time': len(self.free_task_queue) * 40,  # 假设每个任务40秒
            'recent_processing_rate': self._get_recent_processing_rate(),
        }
    
    def _get_oldest_task_age(self) -> int:
        """获取最老任务的年龄（分钟）"""
        if not self.free_task_queue:
            return 0
        
        oldest_task = min(self.free_task_queue, key=lambda x: x['added_time'])
        age = datetime.now() - oldest_task['added_time']
        return int(age.total_seconds() / 60)
    
    def _get_recent_processing_rate(self) -> float:
        """获取最近处理速率（任务/小时）"""
        if len(self.processing_history) < 2:
            return 0
        
        recent_history = self.processing_history[-10:]
        if len(recent_history) < 2:
            return 0
        
        time_span = (recent_history[-1]['processed_time'] - recent_history[0]['processed_time']).total_seconds()
        if time_span == 0:
            return 0
        
        return (len(recent_history) / time_span) * 3600  # 转换为每小时

# 使用示例
def main():
    """使用示例"""
    strategy = FreeQuotaScalingStrategy()
    
    # 模拟实例信息
    instance_info = InstanceInfo(
        instance_id="hai-test-001",
        start_time=datetime.now() - timedelta(days=10),
        disk_usage_gb=65.5,
        network_usage_gb=380.2,
        cpu_utilization=45.0,
        memory_utilization=60.0,
        status="RUNNING",
        cost_accumulated=25.6
    )
    
    # 分析免费额度状态
    free_quota_status = strategy.analyze_free_quota_status(instance_info)
    print(f"免费额度状态: {free_quota_status}")
    
    # 检查是否需要重启
    should_restart, reason = strategy.should_restart_instance(instance_info)
    print(f"是否需要重启: {should_restart}, 原因: {reason}")
    
    # 模拟扩缩容指标
    metrics = ScalingMetrics(
        queue_length=25,
        free_queue_length=15,
        paid_queue_length=10,
        average_wait_time=120,
        current_instances=3,
        free_quota_status=free_quota_status,
        time_of_day=14,  # 下午2点
        is_weekend=False,
        is_holiday=False
    )
    
    # 获取扩缩容计划
    plan = strategy.get_scaling_plan(metrics)
    print(f"扩缩容计划: {plan}")
    
    # 获取动态定价
    pricing = strategy.get_dynamic_pricing(metrics)
    print(f"动态定价: {pricing}")

if __name__ == '__main__':
    main()
